<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="user-scalable=no">
    <title>Sudoku</title>
</head>
<link rel="stylesheet" href="css/index.css">
<body>
    <div class="gameHead">
        <div class="home"></div>
        <div class="level">关卡1</div>
    </div>
    <div class="gameInfo">
        <div id="levelType" class="type">容易</div>
        <div class="errorTimes">错误次数:
            <span>❤️</span>
            <span>❤️</span>
            <span>❤️</span>
        </div>
        <div id="timeTitle" class="time">00:00</div>
    </div>
    <div class="gamePalace">

    </div>
    <div class="gameOption">
        <div class="optionItems">
            <div id="del" class="optionBox">
                <div class="optionImg delImg"></div>
                <div class="optionTitle">擦除</div>
            </div>
<!--            <div id="back" class="optionBox">-->
<!--                <div class="optionImg backImg"></div>-->
<!--                <div class="optionTitle">撤回</div>-->
<!--            </div>-->
            <div id="quickNote" class="optionBox">
                <div class="optionImg quickNoteImg"></div>
                <div class="optionTitle">一键笔记</div>
            </div>
            <div id="note" class="optionBox">
                <div class="optionImg noteImg"></div>
                <div class="optionTitle">开启笔记</div>
            </div>
            <div id="help" class="optionBox">
                <div id="helpTimes" class="optionImg helpImg"></div>
                <div class="optionTitle">提示</div>
            </div>
        </div>
    </div>
    <div class="gameInput">
        <div class="inputItems">
            <div id="input_1" class="inputTitle">1</div>
            <div id="input_2" class="inputTitle">2</div>
            <div id="input_3" class="inputTitle">3</div>
            <div id="input_4" class="inputTitle">4</div>
            <div id="input_5" class="inputTitle">5</div>
            <div id="input_6" class="inputTitle">6</div>
            <div id="input_7" class="inputTitle">7</div>
            <div id="input_8" class="inputTitle">8</div>
            <div id="input_9" class="inputTitle">9</div>
        </div>
    </div>

    <!-- 背景遮罩层 -->
    <div id="menu" class="overlay active">
        <!-- 菜单弹窗 -->
        <div class="popup">
            <div id="easy" class="levelButton">容易</div>
            <div id="middle" class="levelButton">中等</div>
            <div id="hard" class="levelButton">困难</div>
            <div id="master" class="levelButton">大师</div>
        </div>
    </div>

    <div id="victory" class="overlay close">
        <!-- 通关弹窗 -->
        <div class="popup">
            <div class="victory popupTitle">挑战成功</div>
            <div class="popupSmallTitle">恭喜你通过关卡挑战</div>
            <div class="popupSmallTitle">--------关卡信息--------</div>
            <div class="popupBox">
                <div class="popupSmallTitle">难度</div>
                <div id="victoryLevelInfo" class="popupSmallTitle">容易</div>
            </div>
            <div class="popupBox">
                <div class="popupSmallTitle">关卡</div>
                <div id="victoryLevelNum" class="popupSmallTitle">1</div>
            </div>
            <div class="popupBox">
                <div class="popupSmallTitle">使用时间:</div>
                <div id="victorySpendTime" class="popupSmallTitle">05:00</div>
            </div>
            <div id="next" class="popupButton">下一关</div>
            <div class="popupButton backHome">返回主页</div>
        </div>
    </div>

    <div id="defeat" class="overlay close">
        <!-- 失败弹窗 -->
        <div class="popup">
            <div class="defeat popupTitle">挑战失败</div>
            <div class="popupSmallTitle">很遗憾, 您闯关失败</div>
            <div class="popupSmallTitle">--------关卡信息--------</div>
            <div class="popupBox">
                <div class="popupSmallTitle">难度</div>
                <div id="defeatLevelInfo" class="popupSmallTitle">容易</div>
            </div>
            <div class="popupBox">
                <div class="popupSmallTitle">关卡</div>
                <div id="defeatLevelNum" class="popupSmallTitle">1</div>
            </div>
            <div class="popupBox">
                <div class="popupSmallTitle">使用时间:</div>
                <div id="defeatSpendTime" class="popupSmallTitle">05:00</div>
            </div>
            <div id="again" class="popupButton">再次挑战</div>
            <div class="popupButton backHome">返回主页</div>
        </div>
    </div>
</body>
<script>
    // 容易 34-40 中等41-46 困难46-50 大师 51-56
	// 默认游戏等级参数
	var gamelevel;
	// 记录填写错误次数
	var errorNumber;
	// 记录已经填写的数字的数组
	var havefillvalue;
	// 记录当前选择的数字框的坐标
	var focusPosition;
    var errorPosition;
    //输入模式是否笔记模式,true为笔记模式，false为非笔记模式
	var noteModel;
    let quickNoteModel;
    var gameBoard;
    var sudokuGrid;
    let intervalId;
    let setTimeoutId;
    let totalTime = 0;
    let helpTimes;

    // 游戏字体
    var fontFamily = 'system-ui';
	// 游戏面板单元格的背景颜色
	var tdBackgroundColor = '#eee9db';
	// 数字框选中后，单元格已填入的数字与其他已填入数字的单元格和笔记中相同的数字的提示颜色,
	var sameNumberBackgroundColor = '#716e6a';
	// 选中的单元格所在宫、同行、同列的单元格相同的背景颜色
	var relatedBackgroundColor = '#e3dbc4';
	// 选中的单元格,有数字时显示的背景颜色
	var haveNumberBackgroundColor = '#8ba23a';
	// 选中的单元格，没有数字时显示的背景颜色
	var haveNotNumberBackgroundColor = '#e7d2a4';
	// 笔记中的数字颜色
	var noteNumberColor = '#53504d';
	// 单元格已填入的数字颜色
	var fillNumberColor = '#1c858a';
    // 初始单元格数字颜色
    var initNumberColor = '#53504d';
	// 错误提示的数字颜色
	var errorColor = '#e05b5f';
    // 错误笔记提示颜色
    var errorBoxColor = '#e05b5f';
    // 遮罩之后字体颜色
    var underFontColor = '#ffffff';


    // 頁面背景色
    document.body.style.backgroundColor = tdBackgroundColor;

    // 设置游戏字体
    document.body.style.fontFamily = fontFamily;

	// 初始化一个游戏容器div
	var gameDiv = document.getElementsByClassName('gamePalace')[0];

    // cookie 默認值
    let levelInfo = {
        "容易": 1,
        "中等": 1,
        "困难": 1,
        "大师": 1
    }

    // 从cookie中获取关卡信息
    levelInfo = getCookie('levelInfo') === null ? levelInfo : getCookie('levelInfo');

    /**
     * 该函数用于设置或更新Cookie。如果Cookie已经存在，它会自动延长过期时间
     * @param name
     * @param value
     * @param daysToExpire
     */
    function setCookie(name, value, daysToExpire) {
        let cookie = name + "=" + JSON.stringify(value);

        if (daysToExpire) {
            const date = new Date();
            date.setTime(date.getTime() + (daysToExpire * 24 * 60 * 60 * 1000));
            cookie += "; expires=" + date.toUTCString();
        }

        cookie += "; path=/"; // 设置Cookie路径为根路径
        document.cookie = cookie;
    }

    /**
     * 该函数用于获取指定名称的Cookie值
     * @param name
     * @returns {null|string}
     */
    function getCookie(name) {
        const cookieName = name + "=";
        const decodedCookie = decodeURIComponent(document.cookie);
        const cookieArray = decodedCookie.split(';');

        for (let i = 0; i < cookieArray.length; i++) {
            let cookie = cookieArray[i].trim();
            if (cookie.indexOf(cookieName) === 0) {
                const cookieValue = cookie.substring(cookieName.length, cookie.length);
                try {
                    // 尝试将 Cookie 值解析为 JSON 对象
                    return JSON.parse(cookieValue);
                } catch (e) {
                    // 如果解析失败，返回原始值
                    console.warn("Cookie value is not a valid JSON string:", cookieValue);
                    return null;
                }
            }
        }

        return null; // 如果未找到 Cookie，返回 null
    }


    function getRandomNumber(min, max) {
        // 计算范围跨度，包含边界值
        const range = max - min + 1;
        // 生成 [0, range) 之间的随机小数并乘以范围跨度
        const randomDecimal = Math.random() * range;
        // 向下取整得到 [0, range - 1] 之间的整数
        const randomInteger = Math.floor(randomDecimal);
        // 加上最小值，得到 [min, max] 之间的随机整数
        return randomInteger + min;
    }

    let startButtons = document.querySelectorAll('.levelButton');
    let levelType = document.getElementById('levelType');
    let victoryLevelInfo = document.getElementById('victoryLevelInfo');
    let defeatLevelInfo = document.getElementById('defeatLevelInfo');
    startButtons.forEach(button => {
        button.addEventListener('click', function () {
            closePopup()
            switch (this.innerHTML) {
                case '容易':
                    gamelevel = getRandomNumber(34, 40);
                    levelType.innerHTML = '容易';
                    victoryLevelInfo.innerHTML = '容易';
                    defeatLevelInfo.innerHTML = '容易';
                    break;
                case '中等':
                    gamelevel = getRandomNumber(41, 46);
                    levelType.innerHTML = '中等';
                    victoryLevelInfo.innerHTML = '中等';
                    defeatLevelInfo.innerHTML = '中等';
                    break;
                case '困难':
                    gamelevel = getRandomNumber(46, 50);
                    levelType.innerHTML = '困难';
                    victoryLevelInfo.innerHTML = '困难';
                    defeatLevelInfo.innerHTML = '困难';
                    break;
                case '大师':
                    gamelevel = getRandomNumber(51, 56);
                    levelType.innerHTML = '大师';
                    victoryLevelInfo.innerHTML = '大师';
                    defeatLevelInfo.innerHTML = '大师';
                    break;
                default :
                    break;
            }
            console.log('当前游戏难度:', gamelevel);
            // 创建游戏面板
            createGameBoard(0)
        })
    });

    // 首页点击触发
    document.getElementsByClassName('home')[0].addEventListener('click', function () {
        showPopup(0)
    })


    /**
     * 显示弹窗
     * @param flag 0: 显示菜单弹窗, 1: 显示成功弹窗, 2: 显示失败弹窗
     */
    function showPopup(flag) {
        let menuElement = document.getElementById('menu');
        let victoryElement = document.getElementById('victory');
        let defeatElement = document.getElementById('defeat');
        switch (flag) {
            case 0:
                menuElement.classList.remove('close');
                menuElement.classList.add('active');
                break;
            case 1:
                victoryElement.classList.remove('close');
                victoryElement.classList.add('active');
                break;
            case 2:
                defeatElement.classList.remove('close');
                defeatElement.classList.add('active');
                break;
            default:
                break;

        }
    }


    /**
     * 关闭所有弹窗
     */
    function closePopup() {
        let menuElement = document.getElementById('menu');
        let victoryElement = document.getElementById('victory');
        let defeatElement = document.getElementById('defeat');

        menuElement.classList.add('close');
        victoryElement.classList.add('close');
        defeatElement.classList.add('close');

        menuElement.classList.remove('active');
        victoryElement.classList.remove('active');
        defeatElement.classList.remove('active');
    }


    /**
     * 初始化游戏参数
     */
    function init() {
        // 重置错误机会
        errorNumber = 3;
        // 记录已经填写的数字的数组
        havefillvalue = Array.from({ length: 9 }, () => Array(9).fill(0));
        // 记录当前选择的数字框的坐标
        focusPosition = [];
        // 所有错误数字的位置
        errorPosition = Array.from({ length: 9 }, () => Array(9).fill(0));
        // 笔记模式关闭
        noteModel = false;
        // 一鍵筆記模式挂壁
        quickNoteModel = false;
        // 重置提示次数
        helpTimes = 3;
        document.getElementById('helpTimes').innerHTML = helpTimes;
        totalTime = 0;
        // 生命值重置
        document.getElementsByClassName('errorTimes')[0].innerHTML = `错误次数:<span>❤️</span>
                                                                      <span>❤️</span>
                                                                      <span>❤️</span>`
        // 输入数字重置
        setInputTitleStyle(3)
    }

    /**
     *
     * @param type 0:初始化游戏，1:重新开始游戏
     */
	function createGameBoard(type) {
        // 保存 cookie
        setCookie('levelInfo', levelInfo, 7);
        // 设置关卡标题
        document.getElementsByClassName('level')[0].innerHTML = `关卡:${levelInfo[levelType.innerHTML]}`;
        document.getElementById('victoryLevelNum').innerHTML = levelInfo[levelType.innerHTML];
        document.getElementById('defeatLevelNum').innerHTML = levelInfo[levelType.innerHTML];
        init()
        if (type === 0) {
            // 生成数独表格数据
            sudokuGrid = generateSudoku();
            // 按游戏等级生成游戏界面显示的数字
            var gameBoardErrorCount = 0
            while (gameBoardErrorCount < 10) {
                try {
                    gameBoard = randomizeZerosWithUniqueSolution(sudokuGrid, gamelevel);
                    break;
                } catch (error) {
                    gameBoardErrorCount++;
                    console.log('gameBoardErrorCount:', gameBoardErrorCount);
                }
            }
            if (gameBoardErrorCount >= 10) {
                alert("游戏创建失败，请重新开始游戏！");
                return;
            }
        }


		// 清空上次的游戏内容
		gameDiv.innerHTML = '';
		// 动态生成一个9*9的表格
		var table = document.createElement("table");
		// 给table添加样式，添加边框，第3行和第3列加粗，第6行和第6列加粗
		table.style.border = "6px solid #53504d";
        table.style.borderCollapse = 'collapse';
        table.style.margin = '0 auto';
		for(var i=0;i<9;i++){
			var tr = document.createElement("tr");
			for(var j=0;j<9;j++){
				// 创建一个单元格td
				var td = document.createElement("td");
				// 设置单元格id的属性值
				td.setAttribute('id', 'out_td_' + i+"_"+j);
				// 设置单元格坐标的属性值
				td.setAttribute('data-position', i+"_"+j);
				// 设置单元格正确的数字的属性值
				td.setAttribute('data-correctvalue', sudokuGrid[i][j]);
				// 给每个单元格添加点击事件
				td.onclick = function(event) {
					event.stopPropagation();
					// 数字单元格所在的td
					var tdelement = null;
					// 获取当前元素
					const clickelement = event.target;
					// 判断点击的是否笔记模式的td，来确认数字单元格所在的td
					if (clickelement.getAttribute('data-innertd')) {
						tdelement = clickelement.parentElement.parentElement.parentElement;
					} else {
						tdelement = clickelement.parentElement;
					}

					// 获取选择的td单元格的坐标
					focusPosition = tdelement.getAttribute('data-position').split('_');
                    let fillDiv = document.getElementById('fill_' + focusPosition[0] + "_" + focusPosition[1])
                    // 高亮相同数字的单元格
                    sampleNumberHighlight(fillDiv, fillDiv.innerHTML === tdelement.getAttribute('data-correctvalue'))

				}
				// 设置单元格样式
				td.style.color = initNumberColor;
				td.style.backgroundColor = tdBackgroundColor;
				td.style.border = "2px solid #000";
				td.style.textAlign = "center";
				td.style.verticalAlign = "middle";
                td.classList.add('gameNumberTd');
				// 添加边框，来区分每个宫
				if(i===2 || i===5){
					td.style.borderBottom = "6px solid #53504d";
				}
				if(j===2 ||j===5){
                    td.style.borderRight = "6px solid #53504d";
				}
				// 创建用于存放填入的数字容器
				var filldiv = document.createElement("div");
				filldiv.setAttribute('id', 'fill_' + i + '_' + j)
                filldiv.classList.add("gameNumberDiv");
				filldiv.setAttribute('data-filldom', 'out');
				// 将未知的数字单元格属性设置为可编辑
				if (gameBoard[i][j] === 0) {
					td.setAttribute('data-editable', true);
				} else { // 将已知的数字单元格属性设置为不可编辑，并填入已知的数字
					td.setAttribute('data-editable', false);
					filldiv.innerHTML = gameBoard[i][j];
					havefillvalue[i][j] = gameBoard[i][j]
				}
				// 将数字容器添加到数字单元格td中
				td.appendChild(filldiv);
				// 将数字单元格td添加到同行中
				tr.appendChild(td);
				// 在td单元格内，再创建一个3*3的table,用于记录笔记
				var table2 = document.createElement("table");
				table2.setAttribute('id', 'inner_tabindex_' + i + '_' + j);
				table2.style.display = 'none';
                table2.style.borderCollapse = 'collapse';
				var innerValue = 1
				for(var k=0;k<3;k++){
					var tr2 = document.createElement("tr");
					for(var l=0;l<3;l++){
						var td2 = document.createElement("td");
						td2.setAttribute('id', 'inner_td_' + i + '_' + j + '_' + innerValue);
						// 添加属性，用于判断是笔记表格的td还是数字录入的td
						td2.setAttribute('data-filldom', 'inner');
						innerValue = innerValue + 1;
						td2.setAttribute('data-innertd', true);
						td2.style.color = noteNumberColor;
						td2.style.textAlign = "center";
						td2.style.verticalAlign = "middle";
                        td2.classList.add('gameNumberInnerTd');
						tr2.appendChild(td2);
					}
					table2.appendChild(tr2);
				}
				td.appendChild(table2);
			}
			table.appendChild(tr);
		}
		gameDiv.appendChild(table);
		// 开始计时
		startTimer();
	}

    const inputs = document.querySelectorAll('.inputTitle');
    inputs.forEach(input => {
        input.addEventListener('click', function(event) {
            const clickElement = event.target;
            let inputValue = clickElement.innerHTML;
            // 判断当前的输入模式，是否为笔记模式
            if (noteModel) {
                // 获取选择的数字框内笔记表格，并将当前选择的数字进行显示
                const showTd = document.getElementById('inner_td_' + focusPosition[0] + '_' + focusPosition[1] + '_' + inputValue);
                if (showTd.innerHTML) {
                    showTd.innerHTML = '';
                } else {
                    showTd.innerHTML = inputValue;
                }
            } else { // 非笔记模式
                // 获取当前选择的数字框
				const lastFocus = document.getElementById('out_td_' + focusPosition[0] + "_" + focusPosition[1]);
				// 当前选择的数字框应该填入的正确数字值
				const correctValue = lastFocus.getAttribute('data-correctvalue');
				// 如果选择的数字框不可编辑 直接返回，不执行之后的代码
				if (lastFocus.getAttribute('data-editable') === 'false' || clickElement.classList.contains('disable')) {
					return;
				}
				// 获取当前选择的数字框的dom元素
				let fillDiv = document.getElementById('fill_' + focusPosition[0] + '_' + focusPosition[1]);
				// 如果数字框内的值是正确的不在填入数字，否则填入新的数字
				if (fillDiv.innerHTML && fillDiv.innerHTML === correctValue) {
					return;
				} else {
					fillDiv.innerHTML = inputValue;
				}
				// 如果填入的数字不是正确的数字，标红进行提示, 并记录错误次数
				if (correctValue !== inputValue) {
					fillDiv.parentElement.style.color = underFontColor;
                    // 记录错误位置
                    errorPosition[focusPosition[0]][focusPosition[1]] = 1;
                    // 进行扣血操作
                    let errorParent = document.getElementsByClassName('errorTimes')[0];
                    errorParent.children[0].remove()
                    errorNumber--;
                    // 同时当前选中的单元格，所在的宫中将记录的笔记清除掉
                    clearInnerTd()
                    // 高亮相同数字单元格
                    sampleNumberHighlight(fillDiv, false)
				} else { // 如果填入的数字是正确的，将单元格字体颜色设置为白色
					fillDiv.parentElement.style.color = underFontColor;
                    // 更新错误位置
                    errorPosition[focusPosition[0]][focusPosition[1]] = 0;
                    // 高亮相同数字单元格
                    sampleNumberHighlight(fillDiv, true)
					// 同时当前选中的单元格，所在的行、列中将记录的笔记中的相同的数字清除掉
					for(var k=0;k<9;k++){
						document.getElementById('inner_td_' + focusPosition[0] + "_" + k + '_' + inputValue).innerHTML = '';
						document.getElementById('inner_td_' + k + "_" + focusPosition[1] + '_' + inputValue).innerHTML = '';
					}
					// 同时当前选中的单元格，所在的宫中将记录的笔记清除掉
                    clearBoxSameNote(focusPosition[0], focusPosition[1], inputValue);
				}
				// 数字填入后，隐藏笔记，显示填入的数字
				let innerTab = document.getElementById('inner_tabindex_' + focusPosition[0] + '_' + focusPosition[1]);
				fillDiv.style.display = 'block';
				innerTab.style.display = 'none';
				// 记录已经填写的数字
				havefillvalue[focusPosition[0]][focusPosition[1]] = Number(inputValue)
				// 验证所有数字是否已经正确填入，正确的话，则提示游戏通关
                valideAllFillValue();
            }

        })
    })

    function clearInnerTd(){
        for (let i = 1; i <= 9; i++) {
            document.getElementById('inner_td_' + focusPosition[0] + "_" + focusPosition[1] + '_' + i).innerHTML = '';
        }
    }

    /**
     * 清除宫内相同的笔记
     * @param x
     * @param y
     * @param value
     */
    function clearBoxSameNote(x, y, value) {
        // 计算选择的单元格所在宫的起始位置坐标
        const boxRowStart = Math.floor(x / 3) * 3;	// 横坐标
        const boxColStart = Math.floor(y / 3) * 3;	// 纵坐标
        // 将单元格所在宫内的单元格笔记去除相同的,
        for (let i = 0; i < 3; i++) {
            for (let j = 0; j < 3; j++) {
                for (let k = 1; k <= 9; k++) {
                    let innerTd = document.getElementById(`inner_td_${boxRowStart + i}_${boxColStart + j}_${k}`)
                    if (innerTd.innerHTML === value){
                        innerTd.innerHTML = '';
                    }
                }
            }
        }
    }


	// 验证所有数字是否已经正确填入
	function valideAllFillValue() {
        if (errorNumber === 0) {
            // 停止游戏计时
            stopTimer();
            showPopup(2)
        }
		var isAllRight = true;
		for(var i=0;i<9;i++){
			for(var j=0;j<9;j++){
				if (sudokuGrid[i][j] !== havefillvalue[i][j]) {
					isAllRight = false;
				}
			}
		}
		if (isAllRight) {
            // 停止游戏计时
            stopTimer();
            showPopup(1)
            // 更新cookie中关卡信息
            levelInfo[levelType.innerHTML] += 1;
            setCookie("levelInfo", levelInfo, 7);
		}
	}

    // 下一关
    let nextElement = document.getElementById('next');
    nextElement.addEventListener('click', function (event) {
        closePopup();
        createGameBoard(0);
    })

    // 重玩
    let replayElement = document.getElementById('again');
    replayElement.addEventListener('click', function (event) {
        closePopup();
        createGameBoard(1);
    })

    // 回到菜单选择
    let backHomeElement = document.querySelectorAll('.backHome');
    backHomeElement.forEach(back => {
        back.addEventListener("click", function (event) {
            closePopup();
            showPopup(0);
        });
    });

    /**
     * 设置输入数字样式
     * 1. 非笔记模式，如果选择的数字框已填入正确的数字，输入数字标记为灰色, 意为不可输入
     * 2. 笔记模式, 所有输入数字都变成橙色
     * @param flag 0: 填入的是正确数字， 1: 填入的错误数字, 2: 未填入数字, 3: 初始化
     */
    function setInputTitleStyle(flag) {
        inputs.forEach(input => {
            switch (flag) {
                // 填入正确的数字
                case 0:
                    input.classList.remove('noteInput')
                    input.classList.add('disable');
                    break;
                // 填入错误的数字
                case 1:
                    // 非笔记模式
                    if (!noteModel) {
                        input.classList.remove('noteInput', 'disable');
                    } else {
                        input.classList.remove('noteInput')
                        input.classList.add('disable');
                    }
                    break;
                // 未填入数字
                case 2:
                    // 非笔记模式
                    if (!noteModel) {
                        input.classList.remove('noteInput', 'disable');
                    } else {
                        input.classList.add('noteInput');
                        input.classList.remove('disable');
                    }
                    break;
                case 3:
                    input.classList.remove('disable', 'noteInput', 'hidden');
                    break;
                default:
                    break;
            }
        })
    }

    const noteElement = document.getElementById('note');
    noteElement.addEventListener('click', function(event) {

        // 在按钮上显示当前的模式
		if (!noteModel) {
			noteElement.children[1].innerHTML = '关闭笔记';
		} else {
			noteElement.children[1].innerHTML = '开启笔记';
		}
        // 切换模式后，控制笔记是否显示
		for(let i=0;i<9;i++){
			for(let j=0;j<9;j++){
				let outTd = document.getElementById('out_td_' + i + '_' + j);
				let innerTab = document.getElementById('inner_tabindex_' + i + '_' + j);	// 笔记框
				let fillDiv = document.getElementById('fill_' + i + '_' + j);	// 数字录入框
				if (outTd.getAttribute('data-editable') === 'true') {
					if (noteModel) { // 笔记模式，如果选择的数字框已填入正确的数字，则不显示笔记，否则显示笔记
						if (fillDiv.innerHTML) {
							fillDiv.style.display = 'block';
							innerTab.style.display = 'none';
						} else {
							fillDiv.style.display = 'none';
							innerTab.style.display = 'block';
						}
					} else { // 非笔记模式，如果选择的数字框已经填入数字，则不显示笔记，否则显示笔记
						if (fillDiv.innerHTML === '') {
							fillDiv.style.display = 'none';
							innerTab.style.display = 'block';
						} else {
							fillDiv.style.display = 'block';
							innerTab.style.display = 'none';
						}
					}
				}
			}
		}
        noteModel = !noteModel
        try {
            let fillElement = document.getElementById('fill_' + focusPosition[0] + '_' + focusPosition[1]);
            if (fillElement.innerHTML === ''){
                setInputTitleStyle(2);
                // 将可以填的数字高亮
                canFillNumberHighlight()
            } else {
                if (fillElement.innerHTML === fillElement.parentElement.getAttribute('data-correctvalue')) {
                    setInputTitleStyle(0);
                } else if (fillElement.innerHTML !== fillElement.parentElement.getAttribute('data-correctvalue')) {
                    setInputTitleStyle(1);
                    // 将可以填的数字高亮
                    canFillNumberHighlight()
                }
            }

        } catch (e){
            setInputTitleStyle(2);
        }
    })

    const quickNoteElement = document.getElementById('quickNote');
    quickNoteElement.addEventListener('click', function(event) {
        quickNoteModel = !quickNoteModel;
        for(let i=0;i<9;i++) {
            for (let j = 0; j < 9; j++) {
                let fillDiv = document.getElementById('fill_' + i + '_' + j);
                let innerTab = document.getElementById(`inner_tabindex_${i}_${j}`);
                // 只处理未填写过数字的格子
                if (fillDiv.innerHTML === "") {
                    let isPossibleNumber = canFillNumber(i, j);
                    // 快速笔记模式, 将空的位置补上笔记
                    if (quickNoteModel) {
                        for (const fill of isPossibleNumber) {
                            document.getElementById('inner_td_' + i + "_" + j + '_' + fill).innerHTML = fill;
                        }
                        innerTab.style.display = 'block';
                        fillDiv.style.display = 'none';
                    } else {
                        // 清除所有笔记
                        for (let k = 1; k <= 9; k++) {
                            document.getElementById('inner_td_' + i + "_" + j + '_' + k).innerHTML = '';
                        }
                        innerTab.style.display = 'none';
                        fillDiv.style.display = 'block';
                    }
                }

            }
        }

    })

    let delElement = document.getElementById('del');
    delElement.addEventListener('click', function(event) {
        // 获取当前选择的数字框的dom元素
		var filldiv = document.getElementById('fill_' + focusPosition[0] + '_' + focusPosition[1]);
		// 获取当前选择的数字框
		const lastfocus = document.getElementById('out_td_' + focusPosition[0] + "_" + focusPosition[1]);
		// 只能在非笔记模式下，且只可以清除可编辑的数字框内的数据
		if (lastfocus.getAttribute('data-editable') === 'true' && filldiv.innerHTML !== lastfocus.getAttribute('data-correctvalue')) {
			filldiv.innerHTML = '';
            // 清除数字后，检查错误记录是否需要更新
            let errorPositionFlag = errorPosition[focusPosition[0]][focusPosition[1]]
            if (errorPositionFlag === 1) {
                errorPosition[focusPosition[0]][focusPosition[1]] = 0;
            }
            sampleNumberHighlight(filldiv, false)
		}
    });

    let helpElement = document.getElementById('help');
    helpElement.addEventListener('click', function(event) {
        try{
            if (helpTimes > 0) {
                let fillElement = document.getElementById('fill_' + focusPosition[0] + '_' + focusPosition[1]);
                // 笔记框
                let innerTab = document.getElementById('inner_tabindex_' + focusPosition[0] + '_' + focusPosition[1]);
                let correctValue = fillElement.parentElement.getAttribute('data-correctvalue');
                if (fillElement.innerHTML === '' || fillElement.innerHTML !== correctValue) {
                    fillElement.innerHTML = correctValue;
                    // 提示之后显示数字
                    fillElement.style.display = 'block';
                    // 隐藏笔记
                    innerTab.style.display = 'none';
                    // 提示后, 检查错误记录是否需要更新
                    let errorPositionFlag = errorPosition[focusPosition[0]][focusPosition[1]]
                    if (errorPositionFlag === 1) {
                        errorPosition[focusPosition[0]][focusPosition[1]] = 0;
                    }
                    havefillvalue[focusPosition[0]][focusPosition[1]] = Number(fillElement.innerHTML);
                    // 减少提示数
                    helpTimes--;
                    document.getElementById('helpTimes').innerHTML = helpTimes;
                    sampleNumberHighlight(fillElement, true)
                    // 同时当前选中的单元格，所在的行、列中将记录的笔记中的相同的数字清除掉
                    for (let k = 0; k < 9; k++) {
                        document.getElementById('inner_td_' + focusPosition[0] + "_" + k + '_' + correctValue).innerHTML = '';
                        document.getElementById('inner_td_' + k + "_" + focusPosition[1] + '_' + correctValue).innerHTML = '';
                    }
                    // 同时当前选中的单元格，所在的宫中将记录的笔记清除掉
                    clearBoxSameNote(focusPosition[0], focusPosition[1], correctValue);
                    // 验证结果
                    valideAllFillValue();
                }
            } else {
                alert('提示次数已用完')
            }
        }catch (e) {
            console.log("未选择填入框, 无法提示")
        }

    });


    let timer = document.getElementById('timeTitle');

    /**
     * 将毫秒格式化为时间字符串
     * @param {number} ms - 需要格式化的毫秒数
     * @returns {string} 格式化后的时间字符串，格式为hh:mm:ss
     */
    function formatTime(ms) {
        // 将毫秒转换为总秒数
        const totalSeconds = Math.floor(ms / 1000);
        // 计算分钟数
        const minutes = Math.floor(totalSeconds / 60);
        // 计算秒数
        const seconds = Math.floor(totalSeconds % 60);

        // 将小时、分钟和秒数格式化为字符串，不足两位前补0，并用冒号连接
        return [
            pad(minutes),
            pad(seconds)
        ].join(':');
    }

    /**
     * 在数字小于10时，在其左侧填充0
     * @param {number} num - 需要填充的数字
     * @returns {string} 填充后的两位字符串
     */
    function pad(num) {
        // 将数字转换为字符串，并在左侧填充0直到长度为2
        return num.toString().padStart(2, '0');
    }

    // 初始化定时器，将时间显示设置为00:00:00，总时间重置为0
    function startTimer() {
        timer.innerHTML = '00:00';
        // 如果已存在定时器，则清除，以避免重复计时
        if (intervalId) {
            clearInterval(intervalId);
        }
        // 设置一个新的定时器，每秒更新一次时间显示
        intervalId = setInterval(() => {
            totalTime += 1000;
            timer.innerHTML = formatTime(totalTime)
        }, 1000);

        if (setTimeoutId){
            clearTimeout(setTimeoutId);
        }

        setTimeoutId = setTimeout(() => {
            let fiveMinuteInterval = setInterval(() => {
                if (helpTimes < 3) {
                    helpTimes++;
                    document.getElementById('helpTimes').innerHTML = helpTimes;
                    console.log(`当前提示次数: ${helpTimes}`);
                } else {
                    clearInterval(fiveMinuteInterval);
                }
            }, 1000 * 60 * 5); // 每隔5分钟（300000毫秒）执行一次
        }, 1000 * 60 * 5); // 10分钟后（600000毫秒）执行
    }


    /**
     * 停止定时器函数
     *
     * 本函数用于清除已存在的定时器，通过清除intervalId引用的定时器
     * 来停止其继续执行
     */
    function stopTimer() {
        clearInterval(intervalId); // 清除定时器
        let victorySpendTime = document.getElementById('victorySpendTime');
        let defeatSpendTime = document.getElementById('defeatSpendTime');

        victorySpendTime.innerHTML = timer.innerHTML;
        defeatSpendTime.innerHTML = timer.innerHTML;

    }

	/**
	 * 随机打乱一个数组的顺序
	 *
	 * @param {Array} array - 需要被打乱顺序的数组
	 * @returns {Array} - 返回打乱顺序后的数组
	 */
	function shuffle(array) {
	    // 从数组的最后一个元素开始，逐个向前交换元素
	    for (let i = array.length - 1; i > 0; i--) {
	        // 随机选择一个从0到当前元素位置的索引
	        const j = Math.floor(Math.random() * (i + 1));
	        // 交换当前元素与随机选中的元素的位置
	        [array[i], array[j]] = [array[j], array[i]];
	    }
	    // 返回打乱顺序后的数组
	    return array;
	}

	/**
	 * 生成一个数独谜题
	 *
	 * 该函数使用回溯算法来生成一个完整的数独谜题
	 * 首先创建一个9x9的二维数组来表示数独网格，然后通过递归尝试在每个空位上填入数字
	 * 在尝试填入数字之前，会检查该数字是否符合数独的规则：即在同一行、同一列和同一个3x3宫格内不重复
	 * 如果生成成功，返回填充了数字的二维数组；如果生成失败，返回false
	 *
	 * @returns {Array} 一个9x9的二维数组，表示生成的数独谜题
	 */
	function generateSudoku() {
	    // 数独网格的大小
	    const size = 9;
	    // 初始化数独网格，所有位置填充为0，表示空位
	    const grid = Array.from({ length: size }, () => Array(size).fill(0));

	    /**
	     * 递归解决数独谜题
	     *
	     * 该函数尝试从指定的行和列开始填充数字，如果成功填充完整个网格，则返回true
	     * 如果当前位已经有数字，则跳过当前位，尝试填充下一个位置
	     *
	     * @param {number} row 当前行号
	     * @param {number} col 当前列号
	     * @returns {boolean} 如果成功解决数独谜题，则返回true，否则返回false
	     */
	    function solve(row, col) {
	        // 如果已经填充到最后一行的最后一列，表示数独已经解决，返回true
	        if (row === size) return true;
	        // 如果已经填充到最后一列，跳转到下一行的第一列
	        if (col === size) return solve(row + 1, 0);

	        // 如果当前位置已经有数字，跳过当前位，尝试填充下一个位置
	        if (grid[row][col] !== 0) return solve(row, col + 1);

	        // 生成可能的数字列表，即1到9
	        const possibleNumbers = Array.from({ length: 9 }, (_, i) => i + 1);
	        // 打乱可能的数字列表，以确保生成的数独谜题是随机的
	        shuffle(possibleNumbers);

	        // 遍历可能的数字，尝试填充到当前位置
	        for (const num of possibleNumbers) {
	            // 检查当前数字是否可以填充到当前位置
	            if (isValid(row, col, num)) {
	                // 如果可以，填充数字并尝试填充下一个位置
	                grid[row][col] = num;
	                if (solve(row, col + 1)) return true;
	                // 如果填充失败，回溯，将当前位置重置为0
	                grid[row][col] = 0;
	            }
	        }

	        // 如果所有数字都无法填充到当前位置，返回false
	        return false;
	    }

	    /**
	     * 检查数字是否可以填充到指定的位置
	     *
	     * 该函数检查在同一行、同一列和同一个3x3宫格内是否有重复的数字
	     * 如果没有重复的数字，表示当前数字可以填充到当前位置
	     *
	     * @param {number} row 当前行号
	     * @param {number} col 当前列号
	     * @param {number} num 要填充的数字
	     * @returns {boolean} 如果当前数字可以填充到当前位置，则返回true，否则返回false
	     */
	    function isValid(row, col, num) {
	        // 检查同一行和同一列是否有重复的数字
	        for (let i = 0; i < size; i++) {
	            if (grid[row][i] === num || grid[i][col] === num) return false;
	        }

	        // 计算当前位置所在的3x3宫格的起始行号和列号
	        const boxRowStart = Math.floor(row / 3) * 3;
	        const boxColStart = Math.floor(col / 3) * 3;

	        // 检查同一个3x3宫格内是否有重复的数字
	        for (let i = 0; i < 3; i++) {
	            for (let j = 0; j < 3; j++) {
	                if (grid[boxRowStart + i][boxColStart + j] === num) return false;
	            }
	        }

	        // 如果没有找到重复的数字，表示当前数字可以填充到当前位置
	        return true;
	    }

	    // 从第一行第一列开始尝试填充数字
	    solve(0, 0);
	    // 返回填充了数字的二维数组
	    return grid;
	}

	/**
     * 随机化游戏板上的零，确保生成的谜题具有唯一解
     *
     * 该函数用于根据游戏难度随机化游戏板上的一些数字，将其设置为零。这通常用于游戏的初始化过程，
     * 根据不同的游戏难度，将不同数量的数字清零，以生成不同难度的游戏板。优化后的版本确保生成的谜题具有唯一解。
     *
     * @param {number[][]} board - 一个二维数组，表示初始的游戏板
     * @param {number} gamelevel - 游戏难度级别，决定了将被清零的数字的数量
     * @returns {number[][]} - 返回一个二维数组，表示经过随机化清零后的游戏板
     */
    function randomizeZerosWithUniqueSolution(board, gamelevel) {
        const size = 9;
        const gameBoard = Array.from({ length: size }, () => Array(size).fill(0));
        const positions = [];

        // 复制初始游戏板数据到gameBoard中
        for (let i = 0; i < 9; i++) {
            for (let j = 0; j < 9; j++) {
                positions.push([i, j]);
                gameBoard[i][j] = board[i][j];
            }
        }

        // 回溯算法检查游戏板是否有唯一解
        function hasUniqueSolution(board) {
            let count = 0;
            function solve(board, row = 0, col = 0) {
                if (row === size) {
                    count++;
                    return;
                }
                const nextRow = col === size - 1 ? row + 1 : row;
                const nextCol = (col + 1) % size;

                if (board[row][col] !== 0) {
                    solve(board, nextRow, nextCol);
                } else {
                    for (let num = 1; num <= size; num++) {
                        if (isValid(board, row, col, num)) {
                            board[row][col] = num;
                            solve(board, nextRow, nextCol);
                            board[row][col] = 0;
                        }
                    }
                }
            }

            function isValid(board, row, col, num) {
                for (let i = 0; i < size; i++) {
                    if (board[row][i] === num || board[i][col] === num) {
                        return false;
                    }
                }
                const boxRowStart = Math.floor(row / 3) * 3;
                const boxColStart = Math.floor(col / 3) * 3;
                for (let i = 0; i < 3; i++) {
                    for (let j = 0; j < 3; j++) {
                        if (board[boxRowStart + i][boxColStart + j] === num) {
                            return false;
                        }
                    }
                }
                return true;
            }

            solve(board);
            return count === 1;
        }

        // 逐步增加难度，确保每次随机化后游戏板仍然有唯一解
        let currentLevel = 0;
        while (currentLevel < gamelevel) {
            const index = Math.floor(Math.random() * positions.length);
            const [row, col] = positions[index];
            const temp = gameBoard[row][col];
            gameBoard[row][col] = 0;
            positions.splice(index, 1);

            if (hasUniqueSolution(gameBoard)) {
                currentLevel++;
            } else {
                gameBoard[row][col] = temp;
            }
        }

        return gameBoard;
    }

    /**
     * 获取当前单元格所在宫内单元格
     * @param {number} x - 当前单元格横坐标
     * @param {number} y - 当前单元格纵坐标
     * @returns {*[]}
     */
    function theSameBoxItems(x, y) {
        let sameBoxItems = [];
        // 计算选择的单元格所在宫的起始位置坐标
        const boxRowStart = Math.floor(x / 3) * 3;	// 横坐标
        const boxColStart = Math.floor(y / 3) * 3;	// 纵坐标
        // 将单元格所在宫内的单元格设置相同的背景颜色,
        for (let i = 0; i < 3; i++) {
            for (let j = 0; j < 3; j++) {
                let boxItem = document.getElementById('out_td_' + (boxRowStart + i) + "_" + (boxColStart + j));
                sameBoxItems.push(boxItem);
            }
        }
        return sameBoxItems;

    }


    /**
     * 判断当前单元格是否为唯一数字
     * @param {number} x - 当前单元格横坐标
     * @param {number} y - 当前单元格纵坐标
     */
    function canFillNumber(x, y) {
        let fullNumberSet = new Set(['1', '2', '3', '4', '5', '6', '7', '8', '9']);
        let haveNumberSet = new Set();

        // 同行同列的所有数字添加到haveNumberSet中
        for(let k=0;k<9;k++) {
            let sameRow = document.getElementById('out_td_' + x + "_" + k);
            let sameCol = document.getElementById('out_td_' + k + "_" + y);
            let rowFillNumber = sameRow.firstChild.innerHTML;
            let colFillNumber = sameCol.firstChild.innerHTML;

            if (rowFillNumber !== '' && sameRow.getAttribute('data-correctvalue') === rowFillNumber) {
                haveNumberSet.add(rowFillNumber);
            }
            if (colFillNumber !== '' && sameCol.getAttribute('data-correctvalue') === colFillNumber) {
                haveNumberSet.add(colFillNumber);
            }

        }

        // 同宫内的数字添加到haveNumberSet中
        let sameBoxItems = theSameBoxItems(x, y);
        for (let boxItem of sameBoxItems) {
            let boxFillNumber = boxItem.firstChild.innerHTML;
            if (boxFillNumber !== '' && boxItem.getAttribute('data-correctvalue') === boxFillNumber) {
                haveNumberSet.add(boxFillNumber);
            }
        }

        return [...fullNumberSet].filter((item) => !haveNumberSet.has(item));
    }

    /**
     * 将可以填的数字高亮
     */
    function canFillNumberHighlight() {
        let isPossibleNumber = canFillNumber(focusPosition[0], focusPosition[1]);
            // 非笔记模式下, 提示可能填写的数字
            if (!noteModel) {
                // 先让所有数字不可填写
                setInputTitleStyle(0)
                for (let i = 0; i < isPossibleNumber.length; i++) {
                    let inputElement = document.getElementById('input_' + isPossibleNumber[i]);
                    // 让其可以填写
                    inputElement.classList.remove('disable');
                }
            }
    }


	function solveSudoku(board) {
	    const gameBoard = Array.from({ length: 9 }, () => Array(9).fill(0));
	    // 遍历游戏板，记录所有位置，并将初始游戏板数据复制到gameBoard中
	    for (let i = 0; i < 9; i++) {
	        for (let j = 0; j < 9; j++) {
	            gameBoard[i][j] = board[i][j];
	        }
	    }

        function isValid(board, row, col, num) {
            // 检查行是否有重复
            for (let i = 0; i < 9; i++) {
                if (board[row][i] === num) return false;
            }

            // 检查列是否有重复
            for (let i = 0; i < 9; i++) {
                if (board[i][col] === num) return false;
            }

            // 检查所在宫是否有重复
            const startRow = Math.floor(row / 3) * 3;
            const startCol = Math.floor(col / 3) * 3;
            for (let i = 0; i < 3; i++) {
                for (let j = 0; j < 3; j++) {
                    if (board[startRow + i][startCol + j] === num) return false;
                }
            }

            return true;
        }

        function solve(board) {
            for (let row = 0; row < 9; row++) {
                for (let col = 0; col < 9; col++) {
                    if (board[row][col] === 0) {
                        for (let num = 1; num <= 9; num++) {
                            if (isValid(board, row, col, num)) {
                                board[row][col] = num;
                                if (solve(board)) {
                                    return true;
                                }
                                board[row][col] = 0; // 回溯
                            }
                        }
                        return false; // 无解
                    }
                }
            }
            return true; // 找到解
        }

        solve(gameBoard);
        return gameBoard;
    }

    /**
     * 突出选择数字所在的矩形宫格, 以及行和列, 同时突出所有与选中的数字相同的数字
     * @param fillDiv
     * @param flag 数字是否正确的标志
     */
    function sampleNumberHighlight(fillDiv, flag) {
        let numberCount = 0;
        // 先将所有单元格背景颜色回复默认
        for (let i = 0; i < 9; i++) {
            for (let j = 0; j < 9; j++) {
                let item = document.getElementById('out_td_' + i + "_" + j);
                let itemChild = item.firstChild;
                // 如果是初始化的数字，则恢复默认颜色
                if (item.getAttribute("data-editable") === 'false') {
                    item.style.backgroundColor = tdBackgroundColor;
                    item.style.color = initNumberColor;
                } else {
                    // 填写了数字
                    if (itemChild.innerHTML){
                        // 正确填写的数字，则为填写的颜色
                        if (errorPosition[i][j] === 0) {
                            item.style.backgroundColor = tdBackgroundColor;
                            item.style.color = fillNumberColor;
                        }
                    }else {
                        item.style.backgroundColor = tdBackgroundColor;
                    }

                }

            }
        }
        // 获取当前选择的单元格内已填的数字
        let fillnumber = fillDiv.innerHTML;
        // 如果已填入数字，则将相同数字的单元格背景颜色设置为相同的颜色，同时将笔记中相同数字的字体颜色设置为相同的颜色
        if (fillnumber) {
            // 数字录入单元格
            const outelements = document.querySelectorAll('[data-filldom="out"]');

            // 遍历数字录入单元格，将相同数字的单元格背景颜色设置为相同的颜色
            outelements.forEach((el) => {
                if (el.innerHTML === fillnumber) {
                    el.parentElement.style.backgroundColor = sameNumberBackgroundColor;
                    el.parentElement.style.color = underFontColor;
                    numberCount++;
                }
            });
            if (flag) {
                setInputTitleStyle(0);
            } else {
                setInputTitleStyle(1);
                // 将可以填的数字高亮
                canFillNumberHighlight()
            }
        }else {
            setInputTitleStyle(2)
            // 将可以填的数字高亮
            canFillNumberHighlight()
        }

        // 将与单元格同行、同列的单元格设置相同的背景颜色,
        for(let k=0;k<9;k++){
            let sameRow = document.getElementById('out_td_' + focusPosition[0] + "_" + k);
            let sameCol = document.getElementById('out_td_' + k + "_" + focusPosition[1]);

            sameRow.style.backgroundColor = relatedBackgroundColor;
            sameCol.style.backgroundColor = relatedBackgroundColor;

            if (sameRow.firstChild.innerHTML === fillnumber && sameRow.firstChild !== fillDiv) {
                sameRow.style.color = errorColor;
            }

            if (sameCol.firstChild.innerHTML === fillnumber && sameCol.firstChild !== fillDiv) {
                sameCol.style.color = errorColor;
            }

        }

        // 将单元格所在宫内的单元格设置相同的背景颜色,
        let sameBoxItems = theSameBoxItems(focusPosition[0], focusPosition[1]);
        for (let boxItem of sameBoxItems) {
            if (boxItem.firstChild.innerHTML === fillnumber && boxItem.firstChild !== fillDiv) {
                boxItem.style.color = errorColor;
            }
            boxItem.style.backgroundColor = relatedBackgroundColor;
        }

        // 判断数字单元格是否已录入的数字，使用不同颜色背景
        if (fillnumber) {
            if (flag) {
                // 填入数字正确, 设置背景颜色为已填入数字的背景颜色
                fillDiv.parentElement.style.backgroundColor = haveNumberBackgroundColor;
            } else {
                // 填入数字错误, 设置背景颜色为错误背景颜色
                fillDiv.parentElement.style.backgroundColor = errorBoxColor;
            }
        } else {
            // 没有填写数字
            fillDiv.parentElement.style.backgroundColor = haveNotNumberBackgroundColor;
        }

        // 遍历错误数组，设置错误背景颜色
        let errorNumber = 0;
        for (let i = 0; i < 9; i++) {
            for (let j = 0; j < 9; j++) {
                if (errorPosition[i][j] === 1) {
                    let item = document.getElementById('out_td_' + i + "_" + j);
                    item.style.backgroundColor = errorBoxColor;
                    item.style.color = underFontColor;
                    if (item.firstChild.innerHTML === fillnumber) {
                        errorNumber++;
                    }
                }
            }
        }

        // 根据填的数字是否达到9个, 来判断是否填写完成
        let finalNumberCount = numberCount - errorNumber;
        if (finalNumberCount === 9) {
            document.getElementById('input_' + fillnumber).classList.add('hidden');
        }

    }

</script>
</html>
